package com.zhentao.service.impl;

import com.alibaba.fastjson.JSON;
import com.zhentao.model.schedule.dto.TaskDto;
import com.zhentao.model.schedule.pojo.TaskInfo;
import com.zhentao.model.schedule.pojo.TaskInfoLogs;
import com.zhentao.mapper.TaskInfoLogsMapper;
import com.zhentao.mapper.TaskInfoMapper;
import com.zhentao.service.TaskInfoService;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;

import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Calendar;
import java.util.Date;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @Date 2024/3/6 12:00
 * @Author xdm
 **/
@Service
public class TaskInfoServiceImpl implements TaskInfoService {
    @Autowired
    private TaskInfoMapper taskInfoMapper;
    @Autowired
    private TaskInfoLogsMapper taskInfoLogsMapper;
    @Resource
    private RedisTemplate redisTemplate;



    /**
     * @param name   锁的名字 -key的名字
     * @param expire key的过期时间
     * @return
     */
    public Boolean teyLock(String name, Long expire) {
        String token = null;
        try {


            name = name + "_lock";
            token = UUID.randomUUID().toString(); //value值
            //设置5s过期
            Boolean result = redisTemplate.opsForValue().setIfAbsent(name, token, expire, TimeUnit.SECONDS);
            if (result) {
                //加锁成功
                return true;
            } else {
                return false;
            }
        } finally {
            //释放锁 确保谁加锁谁释放
            if (token.equals(redisTemplate.opsForValue().get(name))) {
                redisTemplate.delete(name);

            }
        }
    }


    @Scheduled(cron = "0 */1 * * * ?")
    public void zSetToList() {
        System.out.println("执行了定时任务，从zSer->list" + System.currentTimeMillis());


        if(teyLock("submit:zSetToList",5L)) {
            // 1.获取当前需要消费的数据
            ZSetOperations zSetOperations = redisTemplate.opsForZSet();
            Set<String> tasks = zSetOperations.rangeByScore("submit:future:1_1", 0, System.currentTimeMillis());

            //2.将这些数据放到list里面
    /*        for (String task : tasks) {
                redisTemplate.opsForList().leftPush("submit:currentTopic:1_1", task);
                System.out.println("成功的将当前需要执行的任务数据刷新到list里面");
                //3.把放入list的数据从zSet里面删除
                System.out.println("把放入list的数据从zSet里面删除");
                zSetOperations.remove("submit:future:1_1", task);
            }*/

            redisTemplate.executePipelined(new RedisCallback<Object>() {
                @Override
                public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
                    //2.将这些数据放到list里面
                    for (String task : tasks) {
                        redisTemplate.opsForList().leftPush("submit:currentTopic:1_1", task);
                        System.out.println("成功的将当前需要执行的任务数据刷新到list里面");
                        //3.把放入list的数据从zSet里面删除
                        System.out.println("把放入list的数据从zSet里面删除");
                        zSetOperations.remove("submit:future:1_1", task);
                    }
                    return null;
                }
            });
        }
    }


    @Override
    public TaskDto poll(int type, int priority) {

        String key = type + "_" + priority;
        String currentTopic = "submit:currentTopic:" + key;
        //rightPop之后数据就从list里面消失了
        String task = (String) redisTemplate.opsForList().rightPop(currentTopic);
        if (StringUtils.isNotBlank(task)) {
            //把从redis里面的数据获取到，解析成对象
            TaskDto taskDto = JSON.parseObject(task, TaskDto.class);
            //修改数据库信息，任务被执行
            return taskDto;
        }

        return null;
    }

    @Override
    public long addTask(TaskDto taskDto) {
        //1.添加任务到数据库
        Long taskId = addTaskToDB(taskDto);
        //2.添加数据到redis
        taskDto.setTaskId(taskId);
        addTaskToRedis(taskDto);
        //3.返回taskId
        return taskId;
    }

    public void addTaskToRedis(TaskDto taskDto) {
        //设置redis的key
        String key = taskDto.getTaskType() + "_" + taskDto.getPriority();
        String currentTopic = "submit:currentTopic:" + key;
        String futureTopic = "submit:future:" + key;

        //获取5分钟之后的毫秒值
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.MINUTE, 5);
        Long nextTime = calendar.getTimeInMillis();

        /**
         *   如果任务的执行时间<=当前的时候,直接存入redis的list,去实时消费
         *   如果>=当前的时间，并且在5分钟之内的任务，存入zSet里面
         *   大于5分钟的任务先保存在数据库里面，然后再同步到redis的zSet里面
         */

        System.out.println(taskDto.getExecuteTime() + ";" + System.currentTimeMillis() + ";" + nextTime);
        if (taskDto.getExecuteTime() <= System.currentTimeMillis()) {
            //直接存redis的list里面
            System.out.println(currentTopic + "存入数据到list" + taskDto);
            redisTemplate.opsForList().leftPush(currentTopic, JSON.toJSONString(taskDto));
        } else if (taskDto.getExecuteTime() <= nextTime) {
            //存入zSet里面
            System.out.println(futureTopic + "存入数据到zSet" + taskDto);
            redisTemplate.opsForZSet().add(futureTopic, JSON.toJSONString(taskDto), taskDto.getExecuteTime());
        }

    }

    public Long addTaskToDB(TaskDto dto) {
        //保存到任务表
        TaskInfo taskInfo = new TaskInfo();
        BeanUtils.copyProperties(dto, taskInfo);
        taskInfo.setExecuteTime(new Date(dto.getExecuteTime()));
        taskInfoMapper.insert(taskInfo);

        //保存任务日志
        TaskInfoLogs taskInfoLogs = new TaskInfoLogs();
        BeanUtils.copyProperties(taskInfo, taskInfoLogs);
        taskInfoLogs.setVersion(1);
        taskInfoLogs.setStatus(0);
        taskInfoLogsMapper.insert(taskInfoLogs);

        return taskInfo.getTaskId();
    }
}
